-- -------------------------------------------------------------------------
-- Lexis Modular Loader (Updated for UTF-8 Support & Cache Safety)
-- -------------------------------------------------------------------------
local SCRIPT_NAME = "ClickUl"
local ROOT_DIR = "ClickUl"

local last_log = { msg = nil, t = 0 }
local function log(msg)
    local str = tostring(msg)
    local now = os.clock and os.clock() or 0
    if last_log.msg == str and (now - last_log.t) < 1.0 then return end
    last_log.msg = str; last_log.t = now

    if _G.debug_sys then
        _G.debug_sys.error("[Loader]", str)
    end
end

local function safe_yield(ms)
    if util and util.yield then pcall(util.yield, ms or 0); return true end
    return false
end

local function safe_gc(mode, arg)
    if collectgarbage then pcall(collectgarbage, mode, arg) end
end

local function load_module(cat, name)
    local path = string.format("%s.%s.%s", ROOT_DIR, cat, name)
    local ok, res = pcall(require, path)
    if not ok then log("Failed to load: "..path.."\n"..tostring(res)); return nil end
    return res
end

if not (util and util.create_thread and input and input.key) then
    if util and util.toast then
        util.toast("["..SCRIPT_NAME.."] FATAL: Missing 'util' or 'input' globals.")
    end
    return
end

util.create_thread(function()
    local debugger = load_module("Lib", "debugger")
    local natives = load_module("Lib", "natives")
    local json = load_module("Lib", "json")
    local lang_sys = load_module("Lib", "lang")
    local io_queue = load_module("Lib", "io_queue")
    local utf8_lib = load_module("Lib", "utf8_utils")

    if debugger then
        _G.debug_sys = debugger
    end
    if json then package.loaded["ClickUl.Lib.json"] = json end

    if not (natives and json and lang_sys and utf8_lib) then
        log("Missing critical libs (natives, json, lang, or utf8_utils)")
        return
    end

    local config_sys = load_module("UI", "theme")
    local ui_sys = load_module("UI", "core")
    local features_sys = load_module("Features", "main")
    local notif_sys = load_module("UI", "notification")

    if not (config_sys and ui_sys and features_sys) then log("Missing core modules"); return end

    -- [NEW] Load features config first for language and debug settings
    local features_cfg = load_module("Config", "features")
    if features_cfg then
        features_cfg.init(nil, debugger)
    end

    -- [UPDATED] Init language system before theme (language config now in features.json)
    lang_sys.init(features_cfg, json, debugger)
    _G._T = function(k) return lang_sys.get(k) end

    config_sys.init(json, debugger, lang_sys)
    if not config_sys.load() then log("Config defaults used") end

    -- [UPDATED] Load debug config from features.json (not theme.json)
    if debugger and features_cfg then
        local debug_cfg = features_cfg.get_debug_config()
        if debug_cfg and next(debug_cfg) then
            debugger.init({
                enabled = debug_cfg.enabled,
                log_level = debug_cfg.log_level,
                show_timestamp = debug_cfg.show_timestamp,
                show_memory = debug_cfg.show_memory,
                show_performance = debug_cfg.show_performance,
                log_to_file = debug_cfg.log_to_file,
            }, notif_sys and notif_sys.show)
            debugger.info("[System]", "Debugger initialized with saved settings from features.json")
        elseif config_sys and config_sys.data and config_sys.data.debug then
            -- [BACKWARD COMPAT] Fall back to theme config if features.json has no debug config yet
            -- Migration will move this to features.json on next Setting.init()
            local debug_cfg_legacy = config_sys.data.debug
            debugger.init({
                enabled = debug_cfg_legacy.enabled,
                log_level = debug_cfg_legacy.log_level,
                show_timestamp = debug_cfg_legacy.show_timestamp,
                show_memory = debug_cfg_legacy.show_memory,
                show_performance = debug_cfg_legacy.show_performance,
                log_to_file = debug_cfg_legacy.log_to_file,
            }, notif_sys and notif_sys.show)
            debugger.info("[System]", "Debugger initialized with legacy settings (will migrate to features.json)")
        else
            debugger.init({}, notif_sys and notif_sys.show)
            debugger.info("[System]", "Debugger initialized with default settings (first run)")
        end

        local lua_ver = _VERSION or "Unknown"
        local jit_info = rawget(_G, "jit")
        local jit_ver = jit_info and jit_info.version or "Not LuaJIT"
        debugger.info("[System]", "Lua Environment: " .. lua_ver .. " | " .. jit_ver)

        local const_test <const> = true
        if const_test then
            debugger.info("[System]", "Lua 5.4 <const> syntax: OK")
        end
    end

    ui_sys.init(config_sys, natives, debugger)
    features_sys.init(ui_sys, config_sys, natives, lang_sys, json, rawget(_G, "lexis"), debugger)

    if input.show_cursor then input.show_cursor(false) end
    if ui_sys.update_colors then ui_sys.update_colors() end
    safe_gc("collect")

    -- [PERF OPT] Tune automatic GC for smoother collection
    -- Lua 5.4 generational GC is better for long-running scripts
    if collectgarbage then
        pcall(collectgarbage, "generational", 20, 200)
    end

    local input_key = input.key
    local insert_code = 0x2D

    local toggle_menu = ui_sys.toggle_menu
    local is_open = ui_sys.is_open
    local draw_window = ui_sys.draw_window
    local draw_notifs = ui_sys.draw_notifications
    local clear_cache = ui_sys.clear_cache

    local feat_on_open = features_sys.on_menu_open
    local feat_on_close = features_sys.on_menu_close
    local feat_draw = features_sys.draw_menu
    local feat_overlays = features_sys.draw_overlays
    local feat_tick = features_sys.on_tick

    -- [CRITICAL FIX] Error circuit breaker to prevent infinite loop and log bloat
    local err_count = 0
    local MAX_CONSECUTIVE_ERRORS = 10

    -- [PERF DIAG] Frame time monitoring for CPU spike diagnosis
    local FRAME_TIME_WARNING_MS = 16
    local slow_frame_count = 0
    local slow_frame_log_cooldown = 0
    local os_clock = os.clock

    -- [PERF OPT V3] Extract main tick function outside loop to avoid per-frame closure creation
    -- Effect: Reduces GC pressure from closure allocation
    local function main_tick()
        local debug_enabled = debugger and debugger.get_config and debugger.get_config().enabled
        local frame_start = debug_enabled and os_clock() or 0

        if input_key(insert_code).just_released then
            toggle_menu()
            if is_open() then
                feat_on_open()
            else
                -- [FIX] Call on_menu_close to save settings (e.g., debug config)
                if feat_on_close then feat_on_close() end
                -- [OPTIMIZATION] Release memory when menu closes
                if clear_cache then clear_cache() end
                safe_gc("collect")
            end
        end

        if input.show_cursor then input.show_cursor(is_open()) end

        if is_open() then
            draw_window(_T("ClickUl"), feat_draw)
        end

        if draw_notifs then draw_notifs() end
        if is_open() and feat_overlays then feat_overlays() end

        feat_tick()

        if debug_enabled then
            local now = os_clock()
            local frame_time_ms = (now - frame_start) * 1000
            if frame_time_ms > FRAME_TIME_WARNING_MS then
                slow_frame_count = slow_frame_count + 1
                if now > slow_frame_log_cooldown then
                    if debugger and debugger.warn then
                        debugger.warn("[PERF]", "Slow frame: " .. string.format("%.1fms", frame_time_ms) ..
                            ", count=" .. slow_frame_count .. ", menu=" .. tostring(is_open()))
                    end
                    slow_frame_log_cooldown = now + 5.0
                end
            end
        end

        -- [ASYNC IO] Process deferred IO queue at frame end
        if io_queue and io_queue.process then
            local frame_start_ms = frame_start * 1000
            io_queue.process(frame_start_ms)
        end

        err_count = 0
    end

    while true do
        local status, err = xpcall(main_tick, debug.traceback)

        if not status then
            err_count = err_count + 1
            log("Runtime Error (" .. err_count .. "/" .. MAX_CONSECUTIVE_ERRORS .. "): " .. tostring(err))

            -- [CRITICAL] Stop script if too many consecutive errors
            if err_count >= MAX_CONSECUTIVE_ERRORS then
                log("FATAL: Too many consecutive errors. Stopping script to prevent log bloat.")
                if debugger and debugger.shutdown then
                    pcall(debugger.shutdown)
                end
                return
            end

            safe_yield(1000)
        end
        safe_yield(0)
    end
end)